package com.artfii.fluentsql.core.jdbc;

import com.artfii.fluentsql.core.TableFieldSpell;
import com.artfii.fluentsql.core.exception.FluentSqlException;
import com.artfii.fluentsql.core.tools.DbUitls;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
import java.util.Random;

/**
 * FluentSQl Connection实现
 */
public class FluentConnection extends WrappedConnection implements ConnectionBuilder {
    private final static Logger logger = LoggerFactory.getLogger(FluentConnection.class);

    private boolean masterDB = false; // 主库标志
    public static DataSource currentDataSource = null;
    private ConnectionProvider connectionProvider = null; //数据库连接提供者，由你所使用的构架创建
    private boolean commited = false;
    public String dbTypeName = null; //数据库类型
    private TableFieldSpell tableFieldSpell = TableFieldSpell.UNDERLINE; //数据表的字段拼写类型
    private Boolean sql_hack_check = false; // SQL注入排查
    private Boolean read_write_diff_way = false; //读写分离
    private boolean readOnly = false;
    private boolean closed = false;
    private Boolean support_transactional = false ; //是否支持数据库事务，默认false，关闭时强制变更autoCommit=true

    public FluentConnection(ConnectionProvider provider, DataSource master) {
        FluentSqlConfig config = new FluentSqlConfig();
        config.setMasterDs(master);
        buildConnection(provider, config);
    }

    public FluentConnection(ConnectionProvider provider, DataSource master, List<DataSource> slaveList) {
        FluentSqlConfig config = new FluentSqlConfig();
        config.setMasterDs(master);
        config.setSlaveList(slaveList);
        buildConnection(provider, config);
    }

    public FluentConnection(ConnectionProvider provider, FluentSqlConfig config) {
        buildConnection(provider, config);
    }

    @Override
    public Connection buildConnection(ConnectionProvider provider, FluentSqlConfig config) {
        this.master = config.getMasterDs();
        this.slaveList = config.getSlaveList();
        this.setSql_hack_check(config.getSql_hack_check());
        this.setRead_write_diff_way(config.getRead_write_diff_way());
        this.tableFieldSpell=TableFieldSpell.UNDERLINE;
        this.connectionProvider = provider;
        this.closed = false;
        base_connection.put(ConnectionBuilder.SqlEngine.fluentsql,this);//初始化连接池
        return this;
    }

    /**
     * 创建模似的连接
     */
    public static FluentConnection buildMockMe() {
        String url = "jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;database_to_upper=false";
        DataSource master = DbUitls.getHikariDataSource(url, "sa", "", "org.h2.Driver", 1, 1);
        return new FluentConnection(new DefaultConnectionProvider(), master);
    }

    public FluentConnection getConnectionOfPool(boolean isRead) {
        return ConnectionBuilder.getConnectionOfPool(isRead);
    }

    /**
     * 取得当前数据库连接，包含读写分离逻辑
     *
     * @param isReadOnly 只读
     * @return
     */
    public FluentConnection getCurrentConnection(boolean isReadOnly) {
        if (isReadOnly) {
            this.jdbcConnection = getReadConn();
        } else {
            this.jdbcConnection = getWriteConn();
        }
        dbTypeName = null == dbTypeName ? getDbTypeName() : dbTypeName;
        if (null != this.jdbcConnection) {
            ConnectionBuilder.connection_pool.add(this);
        }
//        System.err.println("当前数据库连接数量：" + ConnectionBuilder.connection_pool.size());
        return this;
    }

    private Connection getWriteConn() {
        currentDataSource = master;
        if(isClosed() || isReadOnly() || isNotCommited()){
            jdbcConnection = connectionProvider.buildOfDs(currentDataSource);
            if(null != jdbcConnection){
                setTransactionIsolation(TRANSACTION_SERIALIZABLE);
                setMasterDB(true);
                setReadOnly(false);
                closed = false;
            }
        }
        return jdbcConnection;
    }

    private Connection getReadConn() {
        boolean isReadOnly = false;
        if (read_write_diff_way && (slaveList != null && slaveList.size() > 0)) {//启用了读写分离
            currentDataSource = getSlaveDs();
            setMasterDB(false);
            isReadOnly =true;
        }else {
            currentDataSource = master;
            setMasterDB(true);
        }
        this.jdbcConnection = getReadOnlyConnection(currentDataSource);
        if(null != jdbcConnection){
            setReadOnly(isReadOnly);
        }
        return this.jdbcConnection;
    }

    private Connection getReadOnlyConnection(DataSource dataSource) {
        if (isClosed()) {
            jdbcConnection = connectionProvider.buildOfDs(dataSource);
            setAutoCommit(true);
        }
        return jdbcConnection;
    }

    /**
     * 取得从库（从库使用了权重分配策略）
     *
     * @return
     */
    private DataSource getSlaveDs() {
        DataSource slaveDs = null;
        Number dbId = ConnectionUsedStrategy.build().get();//权重分配策略
        if (null != dbId) {
            slaveDs = FluentSqlConfig.default_config.getDataSourceMap().get(dbId);
        } else {
            slaveDs = randomSelectSlaveDs();
            if (null == slaveDs) {
                slaveDs = master;
            }
        }
        return slaveDs;
    }

    /**
     * 在指定的从库列表随机选择一个
     *
     * @return
     */
    private DataSource randomSelectSlaveDs() {
        if (slaveList != null && slaveList.size() > 0) {
            int num = new Random().nextInt(slaveList.size() - 1);
            return slaveList.get(num);
        }
        return null;
    }

    /**
     * 取得数据库类型(mysql,pq...)
     * @return
     */
    public String getDbTypeName() {
        try {
            if (isNotClosed() && StringUtils.isEmpty(dbTypeName)) {
                return getMetaData().getDatabaseProductName();
            }
        } catch (SQLException e) {
            throw new FluentSqlException(e);
        }
        return "";
    }

    public TableFieldSpell getTableFieldSpell() {
        return tableFieldSpell;
    }

    public FluentConnection setTableFieldSpell(TableFieldSpell tableFieldSpell) {
        this.tableFieldSpell = tableFieldSpell;
        return this;
    }

    @Override
    public synchronized void commit() {
        try {
            super.commit();
            commited = true;
            close();
            logger.debug("===fluentsql do commit==");
        } catch (SQLException e) {
            commited = false;
            throw new FluentSqlException("FluentConnection commit onError.", e);
        }
    }

    @Override
    public synchronized void rollback() {
        if (!commited) {
            logger.warn("rollback of transaction.");
            try {
                super.rollback();
            } catch (SQLException e) {
                throw new FluentSqlException("FluentConnection rollback onError.", e);
            }
        }
    }

    @Override
    public void close() {
        try {
            if (this.jdbcConnection != null && !jdbcConnection.isClosed()) {
                jdbcConnection.close();
            }
        } catch (SQLException e) {
            throw new FluentSqlException("FluentConnection close onError.", e);
        }
    }

    @Override
    public void setAutoCommit(boolean autoCommit) {
        try {
            super.setAutoCommit(autoCommit);
        } catch (SQLException e) {
            throw new FluentSqlException("FluentConnection setAutoCommit onError.", e);
        }
    }

    @Override
    public void setReadOnly(boolean readOnly)  {
        try {
            this.readOnly = readOnly;
            this.jdbcConnection.setReadOnly(readOnly);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public boolean isReadOnly() {
        return readOnly;
    }

    public boolean isCommited() {
        return commited;
    }
    public boolean isNotCommited() {
        return !commited;
    }

    public Boolean isCheckHack() {
        return sql_hack_check;
    }

    public void setSql_hack_check(Boolean sql_hack_check) {
        this.sql_hack_check = sql_hack_check;
    }

    public boolean isMasterDB() {
        return masterDB;
    }
    public boolean isNotMasterDB() {
        return !masterDB;
    }

    public void setMasterDB(boolean masterDB) {
        this.masterDB = masterDB;
    }

    public Boolean isRWDiffWay() {
        if (null == read_write_diff_way) return false;
        return read_write_diff_way;
    }

    public void setRead_write_diff_way(Boolean read_write_diff_way) {
        this.read_write_diff_way = read_write_diff_way;
    }

    public Boolean isSupport_transactional() {
        if (null == support_transactional) return false;
        return support_transactional;
    }

    public void setSupport_transactional(Boolean support_transactional) {
        this.support_transactional = support_transactional;
    }

    @Override
    public boolean isClosed(){
        closed = super.isClosed();
        return closed;
    }
    public boolean isNotClosed(){
        return !closed;
    }

}
